package com.letu.aopcache.handler;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.letu.annotation.annotation.cache.Cache;
import com.letu.annotation.annotation.cache.Magic;
import com.letu.aopcache.aop.CacheAopProxyChain;
import com.letu.aopcache.clone.ICloner;
import com.letu.aopcache.exception.CacheCenterConnectionException;
import com.letu.aopcache.interceptor.AutoloadCacheProperties;
import com.letu.aopcache.manager.*;
import com.letu.aopcache.model.*;
import com.letu.aopcache.parser.AbstractScriptParser;
import com.letu.aopcache.pool.DataLoaderFactory;
import com.letu.aopcache.util.CacheUtil;
import com.letu.aopcache.util.MSetParam;
import com.letu.cache.lock.DistributedLock;
import com.letu.cache.lock.RedisDistributedLock;
import com.letu.core.context.ContextUtil;
import com.letu.core.context.StaticContextUtil;
import com.letu.core.model.CacheKeyBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 处理AOP
 */
@Slf4j
public class CacheHandler {
    /**
     * 正在处理中的请求
     */
    public final ConcurrentHashMap<CacheKeyTO, ProcessingTO> processing;

    private final ICacheManager cacheManager;

    private final AutoLoadConfig config;

    private final ICloner cloner;

    private final CacheKeyManager cacheKeyManager;

    private final RelatedCacheManager relatedCacheManager;
    private final MethodMsgManager methodMsgManager;

    private final TableCacheManager tableCacheManager;
    private final AbstractScriptParser scriptParser;

    public final DistributedLock distributedLock;
    private final AutoloadCacheProperties autoloadCacheProperties;

    public CacheHandler(ICacheManager cacheManager, AbstractScriptParser scriptParser, AutoloadCacheProperties autoloadCacheProperties,
                        ICloner cloner, CacheKeyManager cacheKeyManager,
                        RelatedCacheManager relatedCacheManager, MethodMsgManager methodMsgManager,
                        TableCacheManager tableCacheManager, DistributedLock distributedLock) throws IllegalArgumentException {
        if (null == cacheManager) {
            throw new IllegalArgumentException("cacheManager is null");
        }
        if (null == cloner) {
            throw new IllegalArgumentException("cloner is null");
        }
        if (null == scriptParser) {
            throw new IllegalArgumentException("scriptParser is null");
        }
        this.processing = new ConcurrentHashMap<>(autoloadCacheProperties.getConfig().getProcessingMapSize());
        this.cacheManager = cacheManager;
        this.config = autoloadCacheProperties.getConfig();
        this.cloner = cloner;
        this.scriptParser = scriptParser;
        this.cacheKeyManager = cacheKeyManager;
        this.relatedCacheManager = relatedCacheManager;
        this.methodMsgManager = methodMsgManager;
        this.tableCacheManager = tableCacheManager;
        this.distributedLock = distributedLock;
        this.autoloadCacheProperties = autoloadCacheProperties;
    }

    /**
     * 处理@Cache 拦截
     *
     * @param pjp   切面
     * @param cache 注解
     * @return T 返回值
     * @throws Exception 异常
     */
    public Object proceed(CacheAopProxyChain pjp, Cache cache) throws Throwable {
        //caffeine 缓存是否失效
        Object[] arguments = pjp.getArgs();
        Method method = pjp.getMethod();
        Long l = System.currentTimeMillis();

//        log.info("执行方法：" + method.getName() + " start");
        //获取方法信息
        MethodMsg methodMsg = methodMsgManager.assembleMethodMsg(cloner, pjp, cache);
        //生成唯一业务key，方法名+参数唯一key
        CacheKeyTO onlyCacheKeyTO = cacheKeyManager.getOnlyKey(cache, methodMsg);
        //生成接口缓存 cachekey
        CacheKeyTO cacheKey = cacheKeyManager.getInterfaceCacheKey(onlyCacheKeyTO, methodMsg);
        if (null == cacheKey || null == methodMsg.getCacheKeyBuilder()) {
            return getData(pjp);
        }
        CacheWrapper<Object> cacheWrapper = null;
        try {
            // 从缓存中获取数据
            cacheWrapper = this.getAll(cacheKey, method);
            //生成关联关系缓存
            List<CacheKeyTO> relatedKeyToList = null;
            if (StringUtils.isNotBlank(cache.magic().key())) {
                //精准缓存模式，目前只支持id
                relatedKeyToList = cacheKeyManager.getRelatedCacheKeyOfMagic(onlyCacheKeyTO, cache, methodMsg, cacheWrapper, scriptParser);
            } else {
                //hash模式
                relatedKeyToList = cacheKeyManager.getRelatedCacheKey(onlyCacheKeyTO, cache, methodMsg, cacheWrapper);
            }
            if (null != cacheWrapper && isValidData(methodMsg, cacheWrapper, relatedKeyToList)) {
                //为了数据尽可能小，别占用redis太大空间，所以接口数据存储的是主键id，通过主键id去全量表缓存拿数据
                Object result = assembleOutData(methodMsg, cacheWrapper);
                //没有数据，则走数据库，暂时不处理缓存穿透
                if (result != null) {
//                    log.info("緩存方法执行完毕，耗时：" + (System.currentTimeMillis() - l) + " cache end");
//                    log.info("缓存数据：" + JSON.toJSONString(cacheWrapper.getCacheObject()));
                    // TODO: 2023/11/15 在这里再次操作本地缓存存一次
                    if (autoloadCacheProperties.getAopCacheConfig().getCaffeineEnabled() && ContextUtil.getCaffineCacheIsOpen()==1) {
                        cacheWrapper.setObjectList((List<Object>) cacheWrapper.getCacheObject());
                        executeAddCaffeineData(pjp, cache, methodMsg, cacheWrapper, relatedKeyToList, cacheKey);
                    }

                    return result;
                }
            }
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        }
//        DataLoader dataLoader = new DataLoader();
        //使用对象池,减少创建对象成本
        DataLoaderFactory factory = DataLoaderFactory.getInstance();
        CacheWrapper<Object> newCacheWrapper = null;
        DataLoader dataLoader = null;
        try {
            dataLoader = factory.getDataLoader();
            newCacheWrapper = dataLoader.init(pjp, cacheKey, cache, this, methodMsg).loadData().getCacheWrapper();
            if (newCacheWrapper == null || newCacheWrapper.getCacheObject() == null || newCacheWrapper.getObjectList() == null || newCacheWrapper.getObjectList().isEmpty()) {
                //缓存穿透,暂不处理
                return newCacheWrapper.getCacheObject();
            }
            //如果缓存时间为null，则取keybuild上的时间
            if (cache.expire() == 0) {
                newCacheWrapper.setExpire((int) methodMsg.getCacheKeyBuilder().getExpire().getSeconds());
            }
            //添加接口key
            newCacheWrapper.setInterfaceKey(cacheKey.getKey());
            //写入全表缓存 redis
            tableCacheManager.writeCache(cache, methodMsg, newCacheWrapper);
            //写入全表缓存 caffeine
            if (autoloadCacheProperties.getAopCacheConfig().getCaffeineEnabled() && ContextUtil.getCaffineCacheIsOpen()==1) {
                tableCacheManager.writeCacheOfCaffeine(cache, methodMsg, newCacheWrapper);
            }

            //生成关联关系缓存
            List<CacheKeyTO> relatedKeyToList = null;
            if (StringUtils.isNotBlank(cache.magic().key())) {
                //精准缓存模式，目前只支持id和属性
                relatedKeyToList = cacheKeyManager.getRelatedCacheKeyOfMagic(onlyCacheKeyTO, cache, methodMsg, newCacheWrapper, scriptParser);
            } else {
                //hash模式
                relatedKeyToList = cacheKeyManager.getRelatedCacheKey(onlyCacheKeyTO, cache, methodMsg, newCacheWrapper);
            }
            if (CollectionUtil.isNotEmpty(relatedKeyToList)) {
                //写入关联关系缓存 redis
                relatedCacheManager.writeCache(cache, methodMsg, newCacheWrapper, relatedKeyToList, cacheKey);

                if (autoloadCacheProperties.getAopCacheConfig().getCaffeineEnabled() && ContextUtil.getCaffineCacheIsOpen()==1) {
                    //写入关联关系缓存 caffeine
                    relatedCacheManager.writeCacheOfCaffeine(cache, methodMsg, newCacheWrapper, relatedKeyToList, cacheKey);
                }

            }
            //写入接口缓存   redis
            writeCache(pjp, pjp.getArgs(), cache, cacheKey, newCacheWrapper);
            if (autoloadCacheProperties.getAopCacheConfig().getCaffeineEnabled() && ContextUtil.getCaffineCacheIsOpen()==1) {
                //写入接口缓存   caffeine
                writeCacheOfCaffeine(pjp, pjp.getArgs(), cache, cacheKey, newCacheWrapper);
            }
//        log.info("执行方法：" + (System.currentTimeMillis() - l) + " 加入缓存 end");
        } finally {
            //还给对象池
            factory.returnObject(dataLoader);
        }
        if (newCacheWrapper != null) {
            return newCacheWrapper.getCacheObject();
        }
        return null;
    }


    public void executeAddCaffeineData(CacheAopProxyChain pjp, Cache cache, MethodMsg methodMsg, CacheWrapper<Object> cacheWrapper, List<CacheKeyTO> relatedKeyToList, CacheKeyTO cacheKey) throws Exception {
        //写入全表缓存 caffeine
        tableCacheManager.writeCacheOfCaffeine(cache, methodMsg, cacheWrapper);
        if (CollectionUtil.isNotEmpty(relatedKeyToList)) {
            //写入关联关系缓存 caffeine
            relatedCacheManager.writeCacheOfCaffeine(cache, methodMsg, cacheWrapper, relatedKeyToList, cacheKey);
        }
        //写入接口缓存   caffeine
        writeCacheOfCaffeine(pjp, pjp.getArgs(), cache, cacheKey, cacheWrapper);
    }

    /**
     * 查询数据是否有效
     *
     * @param cacheWrapper
     * @return
     */
    public boolean isValidData(MethodMsg methodMsg, CacheWrapper<Object> cacheWrapper, List<CacheKeyTO> relatedKeyTo) throws Exception {
        if (null == cacheWrapper) {
            return false;
        }
        //重启时间大于缓存时间，则重新查询
//        if (StaticContextUtil.getReLoadTime() > cacheWrapper.getLastLoadTime()) {
//            return false;
//        }
        //数据的id集合
        //关联关系是否存在
        List<CacheWrapper<Object>> relateList = null;
        if (autoloadCacheProperties.getAopCacheConfig().getCaffeineEnabled() && ContextUtil.getCaffineCacheIsOpen()==1) {
            //从 caffeine中获取
            relateList = relatedCacheManager.queryCacheOfCaffeine(cacheWrapper, relatedKeyTo);
        }
        if (relateList == null) {
            //从 redis 中获取
            relateList = relatedCacheManager.queryCache(cacheWrapper, relatedKeyTo);
        }


        if (relateList == null || relateList.isEmpty()) {
            return false;
        }
        for (CacheWrapper<Object> cacheWrapper1 : relateList) {
            if (cacheWrapper1 == null) {
                return false;
            }
        }
//        //全表缓存的真实数据是否存在
//        List<Object> list = tableCacheManager.queryCache(methodMsg, cacheWrapper);
//        if (list == null || list.isEmpty()) {
//            return false;
//        }
//        for (Object object : list) {
//            if (object == null) {
//                return false;
//            }
//        }
        return true;
    }


    /**
     * 组装数据
     *
     * @param cacheWrapper
     * @return
     */
    public Object assembleOutData(MethodMsg methodMsg, CacheWrapper<Object> cacheWrapper) throws Exception {
        if (null == cacheWrapper || cacheWrapper.getIds() == null || cacheWrapper.getIds().isEmpty()) {
            //说明是业务级别的空，如查不到数据
            return null;
        }
        //数据的id集合
        //全表缓存的真实数据是否存在
        List<Object> list = null;
        if (autoloadCacheProperties.getAopCacheConfig().getCaffeineEnabled() && ContextUtil.getCaffineCacheIsOpen()==1) {
            //从 caffeine中获取
            list = tableCacheManager.queryCacheOfCaffeine(methodMsg, cacheWrapper);
        }
        if (list == null) {
            //从 redis 中获取
            list = tableCacheManager.queryCache(methodMsg, cacheWrapper);
        }
        if (list == null || list.isEmpty()) {
            return null;
        }
        for (Object object : list) {
            if (object == null) {
                return null;
            }
        }
        //组装数据，可能返回的结果是Ipage或Page
        cacheWrapper.setCacheObject(CacheUtil.assembleResultData(cacheWrapper, list));
        return cacheWrapper.getCacheObject();
    }

    /**
     * 组装数据
     *
     * @param cacheWrapper
     * @return
     */
    public void assembleInData(CacheWrapper<Object> cacheWrapper) throws Exception {
        //data数据
        if (null == cacheWrapper || cacheWrapper.getIds() == null || cacheWrapper.getIds().isEmpty()) {
            //说明是业务级别的空，如查不到数据
            return;
        }
        //置空真实数据，减轻数据重复的redis压力
        cacheWrapper.setCacheObject(CacheUtil.assembleInfaceData(cacheWrapper.getCacheObject()));

    }


    public void writeCache(CacheAopProxyChain pjp, Object[] arguments, Cache cache, CacheKeyTO cacheKey,
                           CacheWrapper<Object> cacheWrapper) throws Exception {
        if (null == cacheKey) {
            return;
        }
        //clone 对泛型没法深拷贝
//        CacheWrapper<Object> objectCacheWrapper = (CacheWrapper<Object>) cacheWrapper.clone();
        CacheWrapper<Object> objectCacheWrapper = SerializationUtils.clone(cacheWrapper);
        Method method = pjp.getMethod();
        List<MSetParam> params = new ArrayList<>();
        if (objectCacheWrapper.getExpire() >= 0) {
            //组装数据：由于只存一份正式数据，所以接口数据只存id
            assembleInData(objectCacheWrapper);
            params.add(new MSetParam(cacheKey, objectCacheWrapper));
        }
        int size = params.size();
        if (size == 1) {
            MSetParam param = params.get(0);
            //减轻数据
            CacheWrapper<Object> result = param.getResult();
            result.setReturnType(null);
            result.setHeader(null);
            result.setArgs(null);
            result.setObjectList(null);

            result.setClassName(null);
            result.setMethodName(null);
            result.setInterfaceKey(null);
            this.setCache(param.getCacheKey(), result, method);
        } else if (size > 1) {
            this.mset(method, params);
            //Caffeine 存储
            this.msetOfCaffeine(method, params);
        }
    }

    public void writeCacheOfCaffeine(CacheAopProxyChain pjp, Object[] arguments, Cache cache, CacheKeyTO cacheKey,
                                     CacheWrapper<Object> cacheWrapper) throws Exception {
        if (null == cacheKey) {
            return;
        }
        //clone 对泛型没法深拷贝
//        CacheWrapper<Object> objectCacheWrapper = (CacheWrapper<Object>) cacheWrapper.clone();
        CacheWrapper<Object> objectCacheWrapper = SerializationUtils.clone(cacheWrapper);
        Method method = pjp.getMethod();
        List<MSetParam> params = new ArrayList<>();
        if (objectCacheWrapper.getExpire() >= 0) {
            //组装数据：由于只存一份正式数据，所以接口数据只存id
            assembleInData(objectCacheWrapper);
            params.add(new MSetParam(cacheKey, objectCacheWrapper));
        }
        int size = params.size();
        if (size == 1) {
            MSetParam param = params.get(0);
            //减轻数据
            CacheWrapper<Object> result = param.getResult();
            result.setReturnType(null);
            result.setHeader(null);
            result.setArgs(null);
            result.setObjectList(null);

            result.setClassName(null);
            result.setMethodName(null);
            result.setInterfaceKey(null);
            //Caffeine 存储
            this.setCacheOfCaffeine(param.getCacheKey(), result, method);
        } else if (size > 1) {
            this.mset(method, params);
            //Caffeine 存储
            this.msetOfCaffeine(method, params);
        }
    }

    /**
     * 直接加载数据（加载后的数据不往缓存放）
     *
     * @param pjp CacheAopProxyChain
     * @return Object
     * @throws Throwable 异常
     */
    private Object getData(CacheAopProxyChain pjp) throws Throwable {
        return getData(pjp, pjp.getArgs());
    }

    public Object getData(CacheAopProxyChain pjp, Object[] arguments) throws Throwable {
        try {
            long startTime = System.currentTimeMillis();
            Object result = pjp.doProxyChain(arguments);
            long useTime = System.currentTimeMillis() - startTime;
            if (config.isPrintSlowLog() && useTime >= config.getSlowLoadTime()) {
                String className = pjp.getTarget().getClass().getName();
                if (log.isWarnEnabled()) {
                    log.warn("{}.{}, use time:{}ms", className, pjp.getMethod().getName(), useTime);
                }
            }
            return result;
        } catch (Throwable e) {
            throw e;
        }
    }


    public AbstractScriptParser getScriptParser() {
        return scriptParser;
    }


    public void setCache(CacheKeyTO cacheKey, CacheWrapper<Object> result, Method method) throws CacheCenterConnectionException {
        cacheManager.setCache(cacheKey, result, method);
    }

    public void setCacheOfCaffeine(CacheKeyTO cacheKey, CacheWrapper<Object> result, Method method) throws CacheCenterConnectionException {
        cacheManager.setCacheOfCaffeine(cacheKey, result, method);
    }


    public Map<CacheKeyTO, CacheWrapper<Object>> mget(Method method, final Type returnType, Set<CacheKeyTO> keySet) throws CacheCenterConnectionException {
        return cacheManager.mget(method, returnType, keySet);
    }

    public void mset(final Method method, final Collection<MSetParam> params) throws CacheCenterConnectionException {
        cacheManager.mset(method, params);
    }

    public void msetOfCaffeine(final Method method, final Collection<MSetParam> params) throws CacheCenterConnectionException {
        cacheManager.msetOfCaffeine(method, params);
    }

    public CacheWrapper<Object> getAll(CacheKeyTO key, Method method) throws CacheCenterConnectionException {
        CacheWrapper<Object> result = null;
        if (autoloadCacheProperties.getAopCacheConfig().getCaffeineEnabled() && ContextUtil.getCaffineCacheIsOpen()==1) {
            //从 caffeine中获取
            result = this.getOfCaffeine(key, method);
        }
        if (result == null) {
            //从 redis 中获取
            result = this.get(key, method);
        }
        return result;
    }

    public CacheWrapper<Object> get(CacheKeyTO key, Method method) throws CacheCenterConnectionException {
        return cacheManager.get(key, method);
    }

    public CacheWrapper<Object> getOfCaffeine(CacheKeyTO key, Method method) throws CacheCenterConnectionException {
        return cacheManager.getOfCaffeine(key, method);
    }


    public void delete(Set<CacheKeyTO> keys) throws CacheCenterConnectionException {
        if (null == keys || keys.isEmpty()) {
            return;
        }
        cacheManager.delete(keys);
    }

    public ICloner getCloner() {
        return cloner;
    }

    public AutoLoadConfig getAutoLoadConfig() {
        return this.config;
    }
}
